﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace SmartMathLibrary.DataMining
{
    /// <summary>
    /// This class provides a Principal Component Analysis by using a covariance matrix.
    /// </summary>
    [Serializable]
    public class PrincipalComponentAnalyserCovarianceMethod
    {
        /// <summary>
        /// The covariance matrix, which is created by the algorithm.
        /// </summary>
        private SquareMatrix covarianceMatrix;

        /// <summary>
        /// The data selector, which fetches the data for the PCA.
        /// </summary>
        private IDataSelector dataSelector;

        /// <summary>
        /// This field holds the result items of the PCA algorithm.
        /// </summary>
        private readonly List<PrincipalComponentAnalysisResultItem> resultItems;

        /// <summary>
        /// Initializes a new instance of the <see cref="PrincipalComponentAnalyserCovarianceMethod"/> class.
        /// </summary>
        /// <param name="dataSelector">The data selector, which fetches the data for the PCA.</param>
        public PrincipalComponentAnalyserCovarianceMethod(IDataSelector dataSelector)
        {
            this.dataSelector = dataSelector;
            this.resultItems = new List<PrincipalComponentAnalysisResultItem>();
        }

        /// <summary>
        /// Gets or sets the data selector, which fetches the data for the PCA.
        /// </summary>
        /// <value>The data selector, which fetches the data for the PCA.</value>
        public IDataSelector DataSelector
        {
            get { return dataSelector; }
            set { dataSelector = value; }
        }

        /// <summary>
        /// Gets the covariance matrix, which is created by the algorithm.
        /// </summary>
        /// <value>The covariance matrix, which is created by the algorithm.</value>
        public SquareMatrix CovarianceMatrix
        {
            get { return covarianceMatrix; }
        }

        /// <summary>
        /// Gets the result items of the PCA algorithm.
        /// </summary>
        /// <value>The result items of the PCA algorithm.</value>
        public List<PrincipalComponentAnalysisResultItem> ResultItems
        {
            get { return resultItems; }
        }

        /// <summary>
        /// Executes the principal component analysis.
        /// </summary>
        public void ExecutePrincipalComponentAnalysis()
        {
            double[] averageValues = new double[this.dataSelector.GetColumnCount()];
            this.covarianceMatrix = new SquareMatrix(this.dataSelector.GetColumnCount());

            this.resultItems.Clear();

            for (int i = 0; i < this.dataSelector.GetColumnCount(); i++)
            {
                double tempuri = 0;

                for (int j = 0; j < this.dataSelector.GetRowCount(); j++)
                {
                    tempuri += this.dataSelector.GetValueAt(j, i);
                }

                averageValues[i] = tempuri / this.dataSelector.GetRowCount();
            }

            for (int i = 0; i < this.dataSelector.GetColumnCount(); i++)
            {
                for (int j = 0; j < this.dataSelector.GetColumnCount(); j++)
                {
                    if (j <= i)
                    {
                        double cov = this.ComputeCovariance(i, averageValues[i], j, averageValues[j]);

                        this.covarianceMatrix.SetValueAtPosition(i, j, cov);
                        this.covarianceMatrix.SetValueAtPosition(j, i, cov);
                    }
                }
            }

            EigenvalueDecomposition decomposition = new EigenvalueDecomposition(this.covarianceMatrix);

            decomposition.ExecuteDecomposition();

            double runningtotal = 0;
            double total = Arrays.Sum(decomposition.RealEigenvalues);

            for (int i = 0; i < decomposition.RealEigenvalues.Length; i++)
            {
                double tempuri = 100.0 * decomposition.RealEigenvalues[i] / total;

                runningtotal += tempuri;

                this.resultItems.Add(new PrincipalComponentAnalysisResultItem(i, decomposition.RealEigenvalues[i],
                                                                              tempuri, runningtotal));
            }
        }

        /// <summary>
        /// Computes the covariance of the specified vectors.
        /// </summary>
        /// <param name="columnX">The vector x.</param>
        /// <param name="averageX">The average value of the vector x.</param>
        /// <param name="columnY">The vector y.</param>
        /// <param name="averageY">The average value of the vector y.</param>
        /// <returns>The covariance value of the two vectors.</returns>
        private double ComputeCovariance(int columnX, double averageX, int columnY, double averageY)
        {
            double result = 0;

            for (int i = 0; i < this.dataSelector.GetRowCount(); i++)
            {
                result += (this.dataSelector.GetValueAt(i, columnX) - averageX)
                          * (this.dataSelector.GetValueAt(i, columnY) - averageY);
            }

            return (1.0 / (this.dataSelector.GetRowCount())) * result;
        }
    }
}